<?php

/**
* phpagi-asmanager.php : PHP Asterisk Manager functions
* Website: http://phpagi.sourceforge.net
*
* Copyright (c) 2004 Matthew Asham <matthewa@bcwireless.net>
* All Rights Reserved.
*
* This software is released under the terms of the GNU Public License v2
*  A copy of which is available from http://www.fsf.org/licenses/gpl.txt
* 
*
*  You are requested to drop me an Email letting me know that you're 
*  using it.  This is more of a courtesy than anything else, but I am 
*  interested to know how it is being used.
*
* @package phpAGI
* @version 2.0
*/


/**
* Written for PHP 4.3.4, should work with older PHP 4.x versions.  
* Please submit bug reports, patches, etc to http://sourceforge.net/projects/phpagi/
* Gracias. :)
*
*/
  define('AST_CONFIG_DIR', '/etc/asterisk/');
  define('AST_SPOOL_DIR', '/var/spool/asterisk/');
  define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/');
  define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf');

  define("AMP_CONF", "/etc/amportal.conf");

  define('AST_DIGIT_ANY', '0123456789#*');

  define('AGI_PORT', 4573);

  define('AGIRES_OK', 200);


  define('AST_STATE_DOWN', 0);
  define('AST_STATE_RESERVED', 1);
  define('AST_STATE_OFFHOOK', 2);
  define('AST_STATE_DIALING', 3);
  define('AST_STATE_RING', 4);
  define('AST_STATE_RINGING', 5);
  define('AST_STATE_UP', 6);
  define('AST_STATE_BUSY', 7);
  define('AST_STATE_DIALING_OFFHOOK', 8);
  define('AST_STATE_PRERING', 9);


/**
* Asterisk Manager class
*
* @link http://www.voip-info.org/wiki-Asterisk+config+manager.conf
* @link http://www.voip-info.org/wiki-Asterisk+manager+API
* @example examples/sip_show_peer.php Get information about a sip peer
* @package phpAGI
*/
class AGI_AsteriskManager
{
	/**
	* Config variables
	*
	* @var array
	* @access public
	*/
	var $config;
	
	/**
	* Socket
	*
	* @access public
	*/
	var $socket = NULL;
	
	/**
	* Server we are connected to
	*
	* @access public
	* @var string
	*/
	var $server;
	
	/**
	* Port on the server we are connected to
	*
	* @access public
	* @var integer
	*/
	var $port;
	
	/**
	* Parent AGI
	*
	* @access private
	* @var AGI
	*/
	var $pagi;
	
	/**
	* Event Handlers
	*
	* @access private
	* @var array
	*/
	var $event_handlers;
	
	/**
	* Constructor
	*
	* @param string $config is the name of the config file to parse or a parent agi from which to read the config
	* @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager']
	*/
	function AGI_AsteriskManager($config=NULL, $optconfig=array())
	{
		// load config
		if(!is_null($config) && file_exists($config))
			$this->config = parse_ini_file($config, true);
		elseif(file_exists(DEFAULT_PHPAGI_CONFIG))
			$this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true);
		
		// If optconfig is specified, stuff vals and vars into 'asmanager' config array.
		foreach($optconfig as $var=>$val)
			$this->config['asmanager'][$var] = $val;
		
		// add default values to config for uninitialized values
		if(!isset($this->config['asmanager']['server'])) $this->config['asmanager']['server'] = 'localhost';
		if(!isset($this->config['asmanager']['port'])) $this->config['asmanager']['port'] = 5038;
		if(!isset($this->config['asmanager']['username'])) $this->config['asmanager']['username'] = 'phpagi';
		if(!isset($this->config['asmanager']['secret'])) $this->config['asmanager']['secret'] = 'phpagi';
	}
	
	/**
	* Send a request
	*
	* @param string $action
	* @param array $parameters
	* @return array of parameters
	*/
	function send_request($action, $parameters=array())
	{
		$req = "Action: $action\r\n";
		foreach($parameters as $var=>$val)
			$req .= "$var: $val\r\n";
		$req .= "\r\n";
		fwrite($this->socket, $req);
		return $this->wait_response();
	}
	
	/**
	* Wait for a response
	*
	* If a request was just sent, this will return the response.
	* Otherwise, it will loop forever, handling events.
	*
	* @param boolean $allow_timeout if the socket times out, return an empty array
	* @return array of parameters, empty on timeout
	*/
	function wait_response($allow_timeout=false)
	{
		$timeout = false;
		do
		{
			$type = NULL;
			$parameters = array();

			if (feof($this->socket))
			return false;
			$buffer = trim(fgets($this->socket, 4096));
			while($buffer != '')
			{
			$a = strpos($buffer, ':');
			if($a)
			{
			if(!count($parameters)) // first line in a response?
			{
			$type = strtolower(substr($buffer, 0, $a));
			if(substr($buffer, $a + 2) == 'Follows')
			{
				// A follows response means there is a miltiline field that follows.
				$parameters['data'] = '';
				$buff = fgets($this->socket, 4096);
				while(substr($buff, 0, 6) != '--END ')
				{
				$parameters['data'] .= $buff;
				$buff = fgets($this->socket, 4096);
				}
			}
			}
		
			// store parameter in $parameters
			$parameters[substr($buffer, 0, $a)] = substr($buffer, $a + 2);
			}
			$buffer = trim(fgets($this->socket, 4096));
			}
		
			// process response
			switch($type)
			{
			case '': // timeout occured
			$timeout = $allow_timeout;
			break;
			case 'event':
			$this->process_event($parameters);
			break;
			case 'response':
			break;
			default:
			$this->log('Unhandled response packet from Manager: ' . print_r($parameters, true));
			break;
			}
		} while($type != 'response' && !$timeout);
		return $parameters;
	}
	
	/**
	* Connect to Asterisk
	*
	* @example examples/sip_show_peer.php Get information about a sip peer
	*
	* @param string $server
	* @param string $username
	* @param string $secret
	* @return boolean true on success
	*/
	function connect($server=NULL, $username=NULL, $secret=NULL)
	{
		// use config if not specified
		if(is_null($server)) $server = $this->config['asmanager']['server'];
		if(is_null($username)) $username = $this->config['asmanager']['username'];
		if(is_null($secret)) $secret = $this->config['asmanager']['secret'];
		
		// get port from server if specified
		if(strpos($server, ':') !== false)
		{
			$c = explode(':', $server);
			$this->server = $c[0];
			$this->port = $c[1];
		}
		else
		{
			$this->server = $server;
			$this->port = $this->config['asmanager']['port'];
		}
		
		// connect the socket
		$errno = $errstr = NULL;
		$this->socket = @fsockopen($this->server, $this->port, $errno, $errstr);
		if(!$this->socket)
		{
			$this->log("Unable to connect to manager {$this->server}:{$this->port} ($errno): $errstr");
			return false;
		}
		
		// read the header
		$str = fgets($this->socket);
		if($str == false)
		{
			// a problem.
			$this->log("Asterisk Manager header not received.");
			return false;
		}
		else
		{
			// note: don't $this->log($str) until someone looks to see why it mangles the logging
		}
		
		// login
		$res = $this->send_request('login', array('Username'=>$username, 'Secret'=>$secret));
		if($res['Response'] != 'Success')
		{
			$this->log("Failed to login.");
			$this->disconnect();
			return false;
		}
		return true;
	}
	
	/**
	* Disconnect
	*
	* @example examples/sip_show_peer.php Get information about a sip peer
	*/
	function disconnect()
	{
		$this->logoff();
		fclose($this->socket);
	}
	
	/**
	* Set Absolute Timeout
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout
	* @param string $channel
	* @param integer $timeout
	*/
	function AbsoluteTimeout($channel, $timeout)
	{
		return $this->send_request('AbsoluteTimeout', array('Channel'=>$channel, 'Timeout'=>$timeout));
	}
	
	/**
	* Change monitoring filename of a channel
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor
	* @param string $channel
	* @param string $file
	*/
	function ChangeMonitor($channel, $file)
	{
		return $this->send_request('ChangeMontior', array('Channel'=>$channel, 'File'=>$file));
	}
	
	/**
	* Execute Command
	*
	* @example examples/sip_show_peer.php Get information about a sip peer
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command
	* @link http://www.voip-info.org/wiki-Asterisk+CLI
	* @param string $commadn
	*/
	function Command($command)
	{	
		return $this->send_request('Command', array('Command'=>$command));
	}
	
	/**
	* Enable/Disable sending of events to this manager
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events
	* @param string $eventmask is either 'on', 'off', or 'system,call,log'
	*/
	function Events($eventmask)
	{
		return $this->send_request('Events', array('EventMask'=>$eventmask));
	}
	
	/**
	* Check Extension Status
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState
	* @param string $exten
	* @param string $context
	* @param string $actionid
	*/
	function ExtensionState($exten, $context, $actionid)
	{
		return $this->send_request('ExtensionState', array('Exten'=>$exten, 'Context'=>$context, 'ActionID'=>$actionid));
	}
	
	/**
	* Gets a Channel Variable
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar
	* @param string $channel
	* @param string $variable
	*/
	function GetVar($channel, $variable)
	{
		return $this->send_request('GetVar', array('Channel'=>$channel, 'Variable'=>$variable));
	}
	
	/**
	* Hangup Channel
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup
	* @param string $channel
	*/
	function Hangup($channel)
	{
		return $this->send_request('Hangup', array('Channel'=>$channel));
	}
	
	/**
	* List IAX Peers
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers
	*/
	function IAXPeers()
	{
		return $this->send_request('IAXPeers');
	}
	
	/**
	* List available manager commands
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands
	*/
	function ListCommands()
	{
		return $this->send_request('ListCommands');
	}
	
	/**
	* Logoff Manager
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff
	*/
	function Logoff()
	{
		return $this->send_request('Logoff');
	}
	
	/**
	* Check Mailbox Message Count
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount
	* @param string $mailbox
	*/
	function MailboxCount($mailbox)
	{
		return $this->send_request('MailboxCount', array('Mailbox'=>$mailbox));
	}
	
	/**
	* Check Mailbox
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus
	* @param string $mailbox
	*/
	function MailboxStatus($mailbox)
	{	
		return $this->send_request('MailboxStatus', array('Mailbox'=>$mailbox));
	}
	
	/**
	* Monitor a channel
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor
	* @param string $channel
	* @param string $file
	*/
	function Monitor($channel, $file)
	{
		return $this->send_request('Monitor', array('Channel'=>$channel, 'File'=>$file));
	}
	
	/**
	* Originate Call
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate
	* @param string $channel
	* @param string $exten
	* @param string $context
	* @param string $priority
	* @param integer $timeout
	* @param string $callerid
	* @param string $variable
	* @param string $account
	* @param string $application
	* @param string $data
	*/
	function Originate($channel, $exten, $context, $priority, $timeout, $callerid, $variable, $account, $application, $data)
	{
		$parameters = array();
		if($channel) $parameters['Channel'] = $channel;
		if($exten) $parameters['Exten'] = $exten;
		if($context) $parameters['Context'] = $context;
		if($priority) $parameters['Priority'] = $priority;
		if($timeout) $parameters['Timeout'] = $timeout;
		if($callerid) $parameters['CallerID'] = $callerid;
		if($variable) $parameters['Variable'] = $variable;
		if($account) $parameters['Account'] = $account;
		if($application) $parameters['Application'] = $application;
		if($data) $parameters['Data'] = $data;
		return $this->send_request('Originate', $parameters);
	}	
	
	/**
	* List parked calls
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls
	*/
	function ParkedCalls()
	{
		return $this->send_request('ParkedCalls');
	}
	
	/**
	* Ping
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping
	*/
	function Ping()
	{
		return $this->send_request('Ping');
	}
	
	/**
	* Queues
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues
	*/
	function Queues()
	{
		return $this->send_request('Queues');
	}
	
	/**
	* Queue Status
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus
	*/
	function QueueStatus()
	{
		return $this->send_request('QueueStatus');
	}
	
	/**
	* Redirect
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect
	* @param string $channel
	* @param string $extrachannel
	* @param string $exten
	* @param string $context
	* @param string $priority
	*/
	function Redirect($channel, $extrachannel, $exten, $context, $priority)
	{
		return $this->send_request('Redirect', array('Channel'=>$channel, 'ExtraChannel'=>$extrachannel, 'Exten'=>$exten,
								'Context'=>$context, 'Priority'=>$priority));
	}
	
	/**
	* Set the CDR UserField
	*/
	function SetCDRUserField()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('SetCDRUserField');
	}
	
	/**
	* Set Channel Variable
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar
	* @param string $channel
	* @param string $variable
	* @param string $value
	*/
	function SetVar($channel, $variable, $value)
	{
		return $this->send_request('SetVar', array('Channel'=>$channel, 'Variable'=>$variable, 'Value'=>$value));
	}
	
	/**
	* List SIP Peers
	*/
	function SIPpeers()
	{
	// XXX need to look at source to find this function...
		return $this->send_request('SIPpeers');
	}
	
	/**
	* Channel Status
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status
	* @param string $channel
	*/
	function Status($channel)
	{	
		return $this->send_request('Status', array('Channel'=>$channel));
	}
	
	/**
	* Stop monitoring a channel
	*
	* @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor
	* @param string $channel
	*/
	function StopMontor($channel)
	{
		return $this->send_request('StopMonitor', array('Channel'=>$channel));
	}
	
	/**
	* Dial over Zap channel while offhook
	*/
	function ZapDialOffhook()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('ZapDialOffhook');
	}
	
	/**
	* Toggle Zap channel Do Not Disturb status OFF
	*/
	function ZapDNDoff()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('ZapDNDoff');
	}
	
	/**
	* Toggle Zap channel Do Not Disturb status ON
	*/
	function ZapDNDon()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('ZapDNDon');
	}
	
	/**
	* Hangup Zap Channel
	*/
	function ZapHangup()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('ZapHangup');
	}
	
	/**
	* Transfer Zap Channel
	*/
	function ZapTransfer()
	{
		// XXX need to look at source to find this function...
		return $this->send_request('ZapTransfer');
	}
	
	/*
	* Log a message
	*
	* @param string $message
	* @param integer $level from 1 to 4
	*/
	function log($message, $level=1)
	{
		if($this->pagi != false)
			$this->pagi->conlog($message, $level);
		else
			error_log(date('r') . ' - ' . $message);
	}
	
	/**
	* Add event handler
	*
	* Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events )
	*   Link - Fired when two voice channels are linked together and voice data exchange commences.
	*   Unlink - Fired when a link between two voice channels is discontinued, for example, just before call completion.
	*   Newexten -
	*   Hangup -
	*   Newchannel -
	*   Newstate -
	*   Reload - Fired when the "RELOAD" console command is executed.
	*   Shutdown -
	*   ExtensionStatus -
	*   Rename -
	*   Newcallerid -
	*   Alarm -
	*   AlarmClear -
	*   Agentcallbacklogoff -
	*   Agentcallbacklogin -
	*   Agentlogoff -
	*   MeetmeJoin -
	*   MessageWaiting -
	*   join -
	*   leave -
	*   AgentCalled -
	*   ParkedCall -
	*   Cdr -
	*   ParkedCallsComplete -
	*   QueueParams -
	*   QueueMember -
	*   QueueStatusEnd -
	*   Status -
	*   StatusComplete -
	*   ZapShowChannels -
	*   ZapShowChannelsComplete -
	*
	* @param string $event type or * for default handler
	* @param string $callback function
	* @return boolean sucess
	*/
	function add_event_handler($event, $callback)
	{
		$event = strtolower($event);
		if(isset($this->event_handlers[$event]))
		{
			$this->log("$event handler is already defined, not over-writing.");
			return false;
		}
		$this->event_handlers[$event] = $callback;
		return true;
	}
	
	/**
	* Process event
	*
	* @access private
	* @param array $parameters
	* @return mixed result of event handler or false if no handler was found
	*/
	function process_event($parameters)
	{
		$ret = false;
		$e = strtolower($parameters['Event']);
		$this->log("Got event.. $e");		
		
		$handler = '';
		if(isset($this->event_handlers[$e])) $handler = $this->event_handlers[$e];
		elseif(isset($this->event_handlers['*'])) $handler = $this->event_handlers['*'];
		
		if(function_exists($handler))
		{
			$this->log("Execute handler $handler");
			$ret = $handler($e, $parameters, $this->server, $this->port);
		}
		else
			$this->log("No event handler for event '$e'");
		return $ret;
	}
	
	/** Show all entries in the asterisk database
	 * @return Array associative array of key=>value
	 */
	function database_show() {
		$r = $this->command("database show");
		
		$data = explode("\n",$r["data"]);
		$db = array();
		
		foreach ($data as $line) {
			$temp = explode(":",$line);
			$db[ trim($temp[0]) ] = trim($temp[1]);
		}
		return $db;
	}
	
	/** Add an entry to the asterisk database
	 * @param string $family	The family name to use
	 * @param string $key		The key name to use
	 * @param mixed $value		The value to add
	 * @return bool True if successful
	 */
	function database_put($family, $key, $value) {
		$r = $this->command("database put ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key)." ".$value);
		return (bool)strstr($r["data"], "success");
	}
	
	/** Get an entry from the asterisk database
	 * @param string $family	The family name to use
	 * @param string $key		The key name to use
	 * @return mixed Value of the key, or false if error
	 */
	function database_get($family, $key) {
		$r = $this->command("database get ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key));
		$data = strpos($r["data"],"Value:");
		if ($data !== false) {
			return trim(substr($r["data"],6+$data));
		} else {
			return false;
		}
	}
	
	/** Delete an entry from the asterisk database
	 * @param string $family	The family name to use
	 * @param string $key		The key name to use
	 * @return bool True if successful
	 */
	function database_del($family, $key) {
		$r = $this->command("database del ".str_replace(" ","/",$family)." ".str_replace(" ","/",$key));
		return (bool)strstr($r["data"], "removed");
	}
	
	/** Delete a family from the asterisk database
	 * @param string $family	The family name to use
	 * @return bool True if successful
	 */
	function database_deltree($family) {
		$r = $this->command("database deltree ".str_replace(" ","/",$family));
		return (bool)strstr($r["data"], "removed");
	}	
	
}
?>